
#include "game_world.h"
#include "game_intrinsicts.h"
#include "game_platform.h"
#include "game.h"
#include "game_entity.h"

// 检查单一坐标 TileRel 是否为规范的（Canonical）。
// TileRel 被认为是规范的，当其值位于 -0.5001 倍的 TileSideInMeters 和 0.5001 倍的 TileSideInMeters
// 之间。 此检查用于确保相对偏移量不超出规范的瓦片尺寸范围。
inline bool32 IsCanonical(real32 ChunkDim, real32 TileRel) {
    // TODO: 修复浮点运算以实现精确计算
    real32 Epsilon = 0.01f;
    bool32 Result = ((TileRel >= -0.5f * ChunkDim - Epsilon) &&  //
                     (TileRel <= 0.5f * ChunkDim + Epsilon));
    return Result;
}

// 检查一个 2D 偏移量 Offset 是否为规范的（Canonical）。
// 偏移量被视为规范，当其 X 和 Y 分量均满足单坐标的规范性检查。
// 此函数在调用 IsCanonical(World, real32) 的基础上，验证 2D 坐标整体的合法性。
bool32 IsCanonical(world *World, v3 Offset) {
    bool32 Result = (IsCanonical(World->ChunkDimInMeters.x, Offset.x) &&  // 检查 X 分量是否规范。
                     IsCanonical(World->ChunkDimInMeters.y, Offset.y) &&  // 检查 Y 分量是否规范。
                     IsCanonical(World->ChunkDimInMeters.z, Offset.z)  // 检查 Z 分量是否规范。
    );
    return Result;
}

// 规范化坐标：调整给定的瓦片坐标和相对位置，确保它们在有效范围内。
// 这个函数处理坐标规范化，确保坐标适应瓦片地图的范围。
void RecanonicalizeCoord(real32 ChunkDim, int32 *Tile, real32 *TileRel) {
    // TODO: 需要做一些不使用除法/乘法的方法来重规范化坐标（RecanonicalizeCoord），
    // 因为这种方法可能会导致坐标回绕回到你刚刚离开的瓦片。

    // 计算偏移量，将当前的相对位置 (*TileRel) 转换为全局瓦片的整数偏移量。
    // 例如，如果 *TileRel 超出当前瓦片范围（大于或小于瓦片边长），
    // 则将超出部分转换为整数瓦片偏移量，并更新 *Tile。

    // 注意：假设世界是环形拓扑，如果你走出一端，你会从另一端回来。
    // 瓦片中心为原点，算出玩家在瓦片中心的位置
    int32 Offset = RoundReal32ToInt32((*TileRel) / ChunkDim);
    *Tile += Offset;

    *TileRel -= (real32)(Offset * ChunkDim);
    // 防止走到另一端
    Assert(IsCanonical(ChunkDim, *TileRel));
}

// 获取规范化位置的函数
world_position MapIntoChunkSpace(world *World, world_position BasePos, v3 Offset) {
    world_position Result = BasePos;

    Result.Offset_ += Offset;
    // 对 X 坐标进行规范化处理
    RecanonicalizeCoord(World->ChunkDimInMeters.x, &Result.ChunkX, &Result.Offset_.x);

    // 对 Y 坐标进行规范化处理
    RecanonicalizeCoord(World->ChunkDimInMeters.y, &Result.ChunkY, &Result.Offset_.y);

    // 对 Z 坐标进行规范化处理
    RecanonicalizeCoord(World->ChunkDimInMeters.z, &Result.ChunkZ, &Result.Offset_.z);

    return Result;  // 返回规范化后的结果
}

#define TILE_CHUNK_SAFE_MAGIN (INT32_MAX / 64)

inline world_chunk **GetWorldChunkInternal(world *World, int32 ChunkX, int32 ChunkY, int32 ChunkZ) {
    TIMED_FUNCTION();
    // 检查坐标是否在安全边界之内
    Assert(ChunkX > -TILE_CHUNK_SAFE_MAGIN);
    Assert(ChunkY > -TILE_CHUNK_SAFE_MAGIN);
    Assert(ChunkZ > -TILE_CHUNK_SAFE_MAGIN);
    Assert(ChunkX < TILE_CHUNK_SAFE_MAGIN);
    Assert(ChunkY < TILE_CHUNK_SAFE_MAGIN);
    Assert(ChunkZ < TILE_CHUNK_SAFE_MAGIN);

    // 计算哈希值，用于确定子块的位置
    // 哈希值是通过将 ChunkX, ChunkY, 和 ChunkZ
    // 这三个坐标乘以不同的系数并相加得到的。这些系数（19, 7,
    // 3）用于混合这些坐标，生成一个唯一的哈希值，这有助于在哈希表中找到对应的子块
    uint32 HashValue = 19 * ChunkX + 7 * ChunkY + 3 * ChunkZ;

    // 获取哈希槽索引
    uint32 HashSlot = HashValue & (ArrayCount(World->ChunkHash) - 1);
    // 将 HashValue 的值与 (ArrayCount(World->ChunkHash) - 1)
    // 进行位与运算，确定子块的索引在哈希表中的位置
    // 由于 ArrayCount(World->ChunkHash) 的值通常是一个2的幂，这使得 -1 是一个掩码，可以快速地将
    // HashValue 限制到数组的有效范围内。
    world_chunk **Chunk = &World->ChunkHash[HashSlot];
    Assert(HashSlot < ArrayCount(World->ChunkHash));
    // 通过 HashSlot 索引从哈希表 ChunkHash 中获取子块指针
    while (*Chunk &&
           // 查找匹配的子块
           !((ChunkX == (*Chunk)->ChunkX)       // 比较X坐标
             && (ChunkY == (*Chunk)->ChunkY)    // 比较Y坐标
             && (ChunkZ == (*Chunk)->ChunkZ)))  // 比较Z坐标
    {
        Chunk = &(*Chunk)->NextInHash;
    }
    return Chunk;
}

world_chunk *RemoveWorldChunk(world *World, int32 ChunkX, int32 ChunkY, int32 ChunkZ) {
    world_chunk **ChunkPtr = GetWorldChunkInternal(World, ChunkX, ChunkY, ChunkZ);
    world_chunk *Result = *ChunkPtr;
    if (Result) {
        *ChunkPtr = Result->NextInHash;
    }

    return (Result);
}

// 获取指定坐标的平铺地图（tile map）指针
inline world_chunk *GetWorldChunk(world *World, int32 ChunkX, int32 ChunkY,
                                  int32 ChunkZ,  //
                                  memory_arena *Arena = 0) {
    world_chunk **ChunkPtr = GetWorldChunkInternal(World, ChunkX, ChunkY, ChunkZ);
    world_chunk *Result = *ChunkPtr;
    // 如果需要分配新的子块
    if (Arena && !Result) {
        // 当前子块X坐标已被占用，且没有链表中的下一个子块
        // 为链表分配一个新的子块
        if (!World->FirstFreeChunk) {
            World->FirstFreeChunk = PushStruct(Arena, world_chunk, NoClear());
            World->FirstFreeChunk->NextInHash = 0;
        }
        Result = World->FirstFreeChunk;
        World->FirstFreeChunk = Result->NextInHash;
        Result->FirstBlock = 0;
        Result->ChunkX = ChunkX;  // 设置子块的X坐标
        Result->ChunkY = ChunkY;  // 设置子块的Y坐标
        Result->ChunkZ = ChunkZ;  // 设置子块的Z坐标

        Result->NextInHash = *ChunkPtr;
        *ChunkPtr = Result;
    }

    return Result;  // 返回找到或新创建的子块
}

// 判断两个 world_position 是否位于同一块瓦片上
inline bool32 AreInSameChunk(world *World, world_position *A, world_position *B) {
    // 确保实体 A 的偏移量是规范的（Canonical），即符合世界（World）的坐标系统约定。
    // 如果偏移量不在规范范围内，将触发断言错误，用于捕获潜在的逻辑错误。
    Assert(IsCanonical(World, A->Offset_));

    // 确保实体 B 的偏移量是规范的（Canonical），同样通过断言验证其符合世界（World）的坐标系统要求。
    // 这一步骤用于保证后续操作基于有效的输入数据。
    Assert(IsCanonical(World, B->Offset_));

    // 比较 A 和 B 的绝对瓦片坐标（X、Y、Z）
    // 如果它们在 X、Y、Z 三个维度上的瓦片坐标都相等，则说明位于同一块瓦片上
    bool32 Result = ((A->ChunkX == B->ChunkX) &&  // 比较绝对 X 坐标
                     (A->ChunkY == B->ChunkY) &&  // 比较绝对 Y 坐标
                     (A->ChunkZ == B->ChunkZ));   // 比较绝对 Z 坐标
    return Result;  // 返回比较结果，true 表示在同一块瓦片上，false 表示不在
}
